﻿using System;
namespace Notoric.Commanding.Holders
{
    /// <summary>
    /// Strongly typed implementation of the <see cref="IValueHolder"/> interface.
    /// </summary>
    /// <typeparam name="T">Type of the value held by this holder.</typeparam>
    public class ValueHolder<T> : IValueHolder
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="ValueHolder{T}"/> instance with an uknown value 
        /// (i.e. <see cref="Notoric.Commanding.Holders.ValueHolder{T}.ValueState"/> set to <see cref="F:Notoric.Commanding.Holders.ValueState.Unknown"/>).
        /// </summary>
        /// <seealso cref="ValueState"/>
        /// <seealso cref="P:Notoric.Commanding.Holders.IValueHolder.ValueState"/>
        public ValueHolder()
        {
            state = ValueState.Unknown;
        }

        /// <summary>
        /// Creates a new <see cref="ValueHolder{T}"/> instance and initializes its value.
        /// </summary>
        /// <param name="value">The value of the holder</param>
        /// <seealso cref="F:Notoric.Commanding.Holders.ValueState.Computed"/>
        public ValueHolder(T value)
        {
            this.value = value;
            state = ValueState.Computed;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the value held by this holder.
        /// </summary>
        /// <value>
        /// The value held by this holder. Setting this property forces a transition to the 
        /// <see cref="F:Notoric.Commanding.Holders.ValueState.Computed"/> state.
        /// </value>
        public T Value
        {
            get 
            {
                if (state == ValueState.Unknown)
                    throw new ValueNotKnownException("The value is not known");
                return value; 
            }
            set
            {
                state = ValueState.Computed;
                this.value = value;
            }
        }

        #endregion

        #region IValueHolder Members

        /// <summary>
        /// Resets the <see cref="Notoric.Commanding.Holders.ValueHolder{T}.ValueState"/> property to the <see cref="F:Notoric.Commanding.Holders.ValueState.Unknown"/> state.
        /// </summary>
        public void UnsetValue()
        {
            state = ValueState.Unknown;
        }

        /// <summary>
        /// Gets or sets the value that this holder holds.
        /// </summary>
        /// <value>
        /// <para>
        /// The value held by this holder. Setting the value automatically forces a transition to the <see cref="F:Notoric.Commanding.Holders.ValueState.Computed"/> state.
        /// </para>
        /// </value>
        /// <exception cref="ValueNotKnownException">
        /// The value is not known (<see cref="Notoric.Commanding.Holders.ValueHolder{T}.ValueState"/> equals <see cref="F:Notoric.Commanding.Holders.ValueState.Unknown"/>).
        /// </exception>
        object IValueHolder.WrappedValue
        {
            get
            {
                return Value;
            }
            set
            {
                Value = (T)value;
            }
        }

        /// <summary>
        /// Gets the value state.
        /// </summary>
        /// <value>
        /// <para>
        /// One of the <see cref="Notoric.Commanding.Holders.ValueState"/> values.
        /// </para>
        /// </value>
        public ValueState ValueState
        {
            get { return state; }
        }

        /// <summary>
        /// Gets the type of the value held by this holder.
        /// </summary>
        public Type ValueType
        {
            get { return typeof(T); }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value.
        /// </summary>
        private T value;
        /// <summary>
        /// Holds the state of the value.
        /// </summary>
        private ValueState state;

        #endregion
    }
}
